<?php
/**
 * Cart totals calculation class.
 *
 * Methods are protected and class is final to keep this as an internal API.
 * May be opened in the future once structure is stable.
 *
 * @author  Automattic
 * @package WooCommerce/Classes
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WC_Cart_Totals class.
 *
 * @since 3.2.0
 */
class Phys_Cart_Totals {

	/**
	 * Reference to cart object.
	 *
	 * @since 3.2.0
	 * @var WC_Cart
	 */
	protected $cart;

	/**
	 * Reference to customer object.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $customer;

	/**
	 * Line items to calculate.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $items = array();

	/**
	 * Fees to calculate.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $fees = array();

	/**
	 * Shipping costs.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $shipping = array();

	/**
	 * Applied coupon objects.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $coupons = array();

	/**
	 * Item/coupon discount totals.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $coupon_discount_totals = array();

	/**
	 * Item/coupon discount tax totals.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $coupon_discount_tax_totals = array();

	/**
	 * Should taxes be calculated?
	 *
	 * @var boolean
	 */
	protected $calculate_tax = true;

	/**
	 * Stores totals.
	 *
	 * @since 3.2.0
	 * @var array
	 */
	protected $totals = array(
		'fees_total'         => 0,
		'fees_total_tax'     => 0,
		'items_subtotal'     => 0,
		'items_subtotal_tax' => 0,
		'items_total'        => 0,
		'items_total_tax'    => 0,
		'total'              => 0,
		'shipping_total'     => 0,
		'shipping_tax_total' => 0,
		'discounts_total'    => 0,
	);

	/**
	 * Sets up the items provided, and calculate totals.
	 *
	 * @since 3.2.0
	 * @throws Exception If missing WC_Cart object.
	 *
	 * @param WC_Cart $cart Cart object to calculate totals for.
	 */
	public function __construct( &$cart = null ) {
		if ( ! is_a( $cart, 'WC_Cart' ) ) {
			throw new Exception( 'A valid WC_Cart object is required' );
		}

		$this->cart          = $cart;
		$this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
		$this->calculate();
	}

	/**
	 * Run all calculations methods on the given items in sequence.
	 *
	 * @since 3.2.0
	 */
	protected function calculate() {
		$this->calculate_item_totals();
		$this->calculate_shipping_totals();
		$this->calculate_fee_totals();
		$this->calculate_totals();
	}

	/**
	 * Get default blank set of props used per item.
	 *
	 * @since  3.2.0
	 * @return array
	 */
	protected function get_default_item_props() {
		return (object) array(
			'object'             => null,
			'tax_class'          => '',
			'taxable'            => false,
			'quantity'           => 0,
			'product'            => false,
			'price_includes_tax' => false,
			'subtotal'           => 0,
			'subtotal_tax'       => 0,
			'total'              => 0,
			'total_tax'          => 0,
			'taxes'              => array(),
		);
	}

	/**
	 * Get default blank set of props used per fee.
	 *
	 * @since  3.2.0
	 * @return array
	 */
	protected function get_default_fee_props() {
		return (object) array(
			'object'    => null,
			'tax_class' => '',
			'taxable'   => false,
			'total_tax' => 0,
			'taxes'     => array(),
		);
	}

	/**
	 * Get default blank set of props used per shipping row.
	 *
	 * @since  3.2.0
	 * @return array
	 */
	protected function get_default_shipping_props() {
		return (object) array(
			'object'    => null,
			'tax_class' => '',
			'taxable'   => false,
			'total'     => 0,
			'total_tax' => 0,
			'taxes'     => array(),
		);
	}

	/**
	 * Should we round at subtotal level only?
	 *
	 * @return bool
	 */
	protected function round_at_subtotal() {
		return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
	}

	/**
	 * Handles a cart or order object passed in for calculation. Normalises data
	 * into the same format for use by this class.
	 *
	 * Each item is made up of the following props, in addition to those returned by get_default_item_props() for totals.
	 *    - key: An identifier for the item (cart item key or line item ID).
	 *  - cart_item: For carts, the cart item from the cart which may include custom data.
	 *  - quantity: The qty for this line.
	 *  - price: The line price in cents.
	 *  - product: The product object this cart item is for.
	 *
	 * @since 3.2.0
	 */
	protected function get_items_from_cart() {
		$this->items = array();

		foreach ( $this->cart->get_cart() as $cart_item_key => $cart_item ) {
			$item                     = $this->get_default_item_props();
			$item->object             = $cart_item;
			$item->tax_class          = $cart_item['data']->get_tax_class();
			$item->taxable            = 'taxable' === $cart_item['data']->get_tax_status();
			$item->price_includes_tax = wc_prices_include_tax();
			$item->quantity           = $cart_item['quantity'];

			/*** Custom ***/
			$total_person = 0;
			$subtotal     = 0;
			$subtotal     += wc_add_number_precision_deep( $cart_item['data']->get_price() ) * $cart_item['quantity'];
			$total_person += $cart_item['quantity'];

			$this->cart->cart_contents[$cart_item_key]['price_adults'] = $cart_item['data']->get_price();


			if ( isset( $cart_item['number_children'] ) ) {
				$number_children = $cart_item['number_children'];

				/*** Update quantity when click update cart ***/
				if ( isset( $_POST['cart'] ) ) {
					$number_children                                              = $cart[$cart_item_key]['number_children'] = $_POST['cart'][$cart_item_key]['number_children'];
					$this->cart->cart_contents[$cart_item_key]['number_children'] = $number_children;
				}

				$price_children = self::get_price_child_tour( $cart_item ) * $number_children;
				$subtotal       += wc_add_number_precision_deep( $price_children );
				$total_person   += (int) $number_children;
			}

			// Variation
			if ( isset( $cart_item['tour_variations'] ) ) {
				$price_variation_tour = self::get_price_variation_tour( $cart_item );
				$subtotal             += wc_add_number_precision_deep( $price_variation_tour );
			}

			// Group discount
			$tour_group_discount_enable = get_post_meta( $cart_item['product_id'], '_tour_group_discount_enable', true );

			if ( $tour_group_discount_enable != '' && $tour_group_discount_enable == 1 ) {
				$tour_group_discount_data = get_post_meta( $cart_item['product_id'], '_tour_group_discount_data', true );


				if ( $tour_group_discount_data != '' ) {
					$tour_group_discount_data_obj = json_decode( $tour_group_discount_data );

					if ( $tour_group_discount_data_obj !== null && json_last_error() === JSON_ERROR_NONE ) {
						$customer_number_match = 0;
						$discount_val          = 0;

						foreach ( $tour_group_discount_data_obj as $item_discount ) {
							$number_customer = (int) ( $item_discount->number_customer );

							if ( $number_customer <= $total_person && $customer_number_match < $number_customer ) {
								$customer_number_match = $number_customer;
								$discount_val          = $item_discount->discount;
							}
						}

						if ( $customer_number_match !== 0 ) {
							$pattern = '/\%$/';
							$this->cart->cart_contents[$cart_item_key]['tour_group_discount'] = __('Group discount').' '.$discount_val.' ('.$total_person.' '.__('People').')';

							if ( preg_match( $pattern, $discount_val ) ) {
								$discount_val = (float) ( $discount_val );

								$subtotal = $subtotal - ( $subtotal * $discount_val / 100 );
							} else {
								$this->cart->cart_contents[$cart_item_key]['tour_group_discount'] = __('Group discount').' '.TravelPhysUtility::tour_format_price($discount_val).' ('.$total_person.' '.__('People').')';

								$subtotal -= wc_add_number_precision_deep($discount_val);
							}
						}
					}
				}
			}

			$item->subtotal = $subtotal;
			/*** End ***/

			$item->product               = $cart_item['data'];
			$item->tax_rates             = $this->get_item_tax_rates( $item );
			$this->items[$cart_item_key] = $item;
		}
	}

	/**
	 * Get item costs grouped by tax class.
	 *
	 * @since  3.2.0
	 * @return array
	 */
	protected function get_tax_class_costs() {
		$item_tax_classes     = wp_list_pluck( $this->items, 'tax_class' );
		$shipping_tax_classes = wp_list_pluck( $this->shipping, 'tax_class' );
		$fee_tax_classes      = wp_list_pluck( $this->fees, 'tax_class' );
		$costs                = array_fill_keys( $item_tax_classes + $shipping_tax_classes + $fee_tax_classes, 0 );
		$costs['non-taxable'] = 0;

		foreach ( $this->items + $this->fees + $this->shipping as $item ) {
			if ( 0 > $item->total ) {
				continue;
			}
			if ( ! $item->taxable ) {
				$costs['non-taxable'] += $item->total;
			} elseif ( 'inherit' === $item->tax_class ) {
				$costs[reset( $item_tax_classes )] += $item->total;
			} else {
				$costs[$item->tax_class] += $item->total;
			}
		}

		return array_filter( $costs );
	}

	/**
	 * Get fee objects from the cart. Normalises data
	 * into the same format for use by this class.
	 *
	 * @since 3.2.0
	 */
	protected function get_fees_from_cart() {
		$this->fees = array();
		$this->cart->calculate_fees();

		$fee_running_total = 0;

		foreach ( $this->cart->get_fees() as $fee_key => $fee_object ) {
			$fee            = $this->get_default_fee_props();
			$fee->object    = $fee_object;
			$fee->tax_class = $fee->object->tax_class;
			$fee->taxable   = $fee->object->taxable;
			$fee->total     = wc_add_number_precision_deep( $fee->object->amount );

			// Negative fees should not make the order total go negative.
			if ( 0 > $fee->total ) {
				$max_discount = round( $this->get_total( 'items_total', true ) + $fee_running_total + $this->get_total( 'shipping_total', true ) ) * - 1;

				if ( $fee->total < $max_discount ) {
					$fee->total = $max_discount;
				}
			}

			$fee_running_total += $fee->total;

			if ( $this->calculate_tax ) {
				if ( 0 > $fee->total ) {
					// Negative fees should have the taxes split between all items so it works as a true discount.
					$tax_class_costs = $this->get_tax_class_costs();
					$total_cost      = array_sum( $tax_class_costs );

					if ( $total_cost ) {
						foreach ( $tax_class_costs as $tax_class => $tax_class_cost ) {
							if ( 'non-taxable' === $tax_class ) {
								continue;
							}
							$proportion               = $tax_class_cost / $total_cost;
							$cart_discount_proportion = $fee->total * $proportion;
							$fee->taxes               = wc_array_merge_recursive_numeric( $fee->taxes, WC_Tax::calc_tax( $fee->total * $proportion, WC_Tax::get_rates( $tax_class ) ) );
						}
					}

				} elseif ( $fee->object->taxable ) {
					$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->tax_class, $this->cart->get_customer() ), false );
				}
			}

			$fee->taxes = apply_filters( 'woocommerce_cart_totals_get_fees_from_cart_taxes', $fee->taxes, $fee, $this );

			$fee->total_tax = array_sum( $fee->taxes );

			if ( ! $this->round_at_subtotal() ) {
				$fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() );
			}

			// Set totals within object.
			$fee->object->total    = wc_remove_number_precision_deep( $fee->total );
			$fee->object->tax_data = wc_remove_number_precision_deep( $fee->taxes );
			$fee->object->tax      = wc_remove_number_precision_deep( $fee->total_tax );

			$this->fees[$fee_key] = $fee;
		}
	}

	/**
	 * Get shipping methods from the cart and normalise.
	 *
	 * @since 3.2.0
	 */
	protected function get_shipping_from_cart() {
		$this->shipping = array();

		foreach ( $this->cart->calculate_shipping() as $key => $shipping_object ) {
			$shipping_line            = $this->get_default_shipping_props();
			$shipping_line->object    = $shipping_object;
			$shipping_line->tax_class = get_option( 'woocommerce_shipping_tax_class' );
			$shipping_line->taxable   = true;
			$shipping_line->total     = wc_add_number_precision_deep( $shipping_object->cost );
			$shipping_line->taxes     = wc_add_number_precision_deep( $shipping_object->taxes );
			$shipping_line->total_tax = wc_add_number_precision_deep( array_sum( $shipping_object->taxes ) );

			if ( ! $this->round_at_subtotal() ) {
				$shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, wc_get_rounding_precision() );
			}

			$this->shipping[$key] = $shipping_line;
		}
	}

	/**
	 * Return array of coupon objects from the cart. Normalises data
	 * into the same format for use by this class.
	 *
	 * @since  3.2.0
	 */
	protected function get_coupons_from_cart() {
		$this->coupons = $this->cart->get_coupons();

		foreach ( $this->coupons as $coupon ) {
			switch ( $coupon->get_discount_type() ) {
				case 'fixed_product' :
					$coupon->sort = 1;
					break;
				case 'percent' :
					$coupon->sort = 2;
					break;
				case 'fixed_cart' :
					$coupon->sort = 3;
					break;
				default:
					$coupon->sort = 0;
					break;
			}
		}

		uasort( $this->coupons, array( $this, 'sort_coupons_callback' ) );
	}

	/**
	 * Sort coupons so discounts apply consistently across installs.
	 *
	 * In order of priority;
	 *    - sort param
	 *  - usage restriction
	 *  - coupon value
	 *  - ID
	 *
	 * @param WC_Coupon $a Coupon object.
	 * @param WC_Coupon $b Coupon object.
	 *
	 * @return int
	 */
	protected function sort_coupons_callback( $a, $b ) {
		if ( $a->sort === $b->sort ) {
			if ( $a->get_limit_usage_to_x_items() === $b->get_limit_usage_to_x_items() ) {
				if ( $a->get_amount() === $b->get_amount() ) {
					return $b->get_id() - $a->get_id();
				}

				return ( $a->get_amount() < $b->get_amount() ) ? - 1 : 1;
			}

			return ( $a->get_limit_usage_to_x_items() < $b->get_limit_usage_to_x_items() ) ? - 1 : 1;
		}

		return ( $a->sort < $b->sort ) ? - 1 : 1;
	}

	/**
	 * Only ran if woocommerce_adjust_non_base_location_prices is true.
	 *
	 * If the customer is outside of the base location, this removes the base
	 * taxes. This is off by default unless the filter is used.
	 *
	 * Uses edit context so unfiltered tax class is returned.
	 *
	 * @since 3.2.0
	 *
	 * @param object $item Item to adjust the prices of.
	 *
	 * @return object
	 */
	protected function adjust_non_base_location_price( $item ) {
		$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'edit' ) );

		if ( $item->tax_rates !== $base_tax_rates ) {
			// Work out a new base price without the shop's base tax.
			$taxes = WC_Tax::calc_tax( $item->subtotal, $base_tax_rates, true, true );

			// Now we have a new item price (excluding TAX).
			$item->subtotal           = $item->subtotal - array_sum( $taxes );
			$item->price_includes_tax = false;
		}

		return $item;
	}

	/**
	 * Get discounted price of an item with precision (in cents).
	 *
	 * @since  3.2.0
	 *
	 * @param  object $item_key Item to get the price of.
	 *
	 * @return int
	 */
	protected function get_discounted_price_in_cents( $item_key ) {
		$item  = $this->items[$item_key];
		$price = isset( $this->coupon_discount_totals[$item_key] ) ? $item->subtotal - $this->coupon_discount_totals[$item_key] : $item->subtotal;

		if ( $item->price_includes_tax ) {
			$price += $item->subtotal_tax;
		}

		return $price;
	}

	/**
	 * Get tax rates for an item. Caches rates in class to avoid multiple look ups.
	 *
	 * @param  object $item Item to get tax rates for.
	 *
	 * @return array of taxes
	 */
	protected function get_item_tax_rates( $item ) {
		$tax_class = $item->product->get_tax_class();

		return isset( $this->item_tax_rates[$tax_class] ) ? $this->item_tax_rates[$tax_class] : $this->item_tax_rates[$tax_class] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->cart->get_customer() );
	}

	/**
	 * Get item costs grouped by tax class.
	 *
	 * @since  3.2.0
	 * @return array
	 */
	protected function get_item_costs_by_tax_class() {
		$tax_classes = array(
			'non-taxable' => 0,
		);

		foreach ( $this->items + $this->fees + $this->shipping as $item ) {
			if ( ! isset( $tax_classes[$item->tax_class] ) ) {
				$tax_classes[$item->tax_class] = 0;
			}

			if ( $item->taxable ) {
				$tax_classes[$item->tax_class] += $item->total;
			} else {
				$tax_classes['non-taxable'] += $item->total;
			}
		}

		return $tax_classes;
	}

	/**
	 * Get a single total with or without precision (in cents).
	 *
	 * @since  3.2.0
	 *
	 * @param  string $key      Total to get.
	 * @param  bool   $in_cents Should the totals be returned in cents, or without precision.
	 *
	 * @return int|float
	 */
	public function get_total( $key = 'total', $in_cents = false ) {
		$totals = $this->get_totals( $in_cents );

		return isset( $totals[$key] ) ? $totals[$key] : 0;
	}

	/**
	 * Set a single total.
	 *
	 * @since  3.2.0
	 *
	 * @param string $key   Total name you want to set.
	 * @param int    $total Total to set.
	 */
	protected function set_total( $key = 'total', $total ) {
		$this->totals[$key] = $total;
	}

	/**
	 * Get all totals with or without precision (in cents).
	 *
	 * @since  3.2.0
	 *
	 * @param  bool $in_cents Should the totals be returned in cents, or without precision.
	 *
	 * @return array.
	 */
	public function get_totals( $in_cents = false ) {
		return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
	}

	/**
	 * Get taxes merged by type.
	 *
	 * @since 3.2.0
	 *
	 * @param  array|string $types Types to merge and return. Defaults to all.
	 *
	 * @return array
	 */
	protected function get_merged_taxes( $in_cents = false, $types = array( 'items', 'fees', 'shipping' ) ) {
		$items = array();
		$taxes = array();

		if ( is_string( $types ) ) {
			$types = array( $types );
		}

		foreach ( $types as $type ) {
			if ( isset( $this->$type ) ) {
				$items = array_merge( $items, $this->$type );
			}
		}

		foreach ( $items as $item ) {
			foreach ( $item->taxes as $rate_id => $rate ) {
				if ( ! isset( $taxes[$rate_id] ) ) {
					$taxes[$rate_id] = 0;
				}
				$taxes[$rate_id] += $rate;
			}
		}

		return $in_cents ? $taxes : wc_remove_number_precision_deep( $taxes );
	}

	/**
	 * Combine item taxes into a single array, preserving keys.
	 *
	 * @since 3.2.0
	 *
	 * @param array $taxes Taxes to combine.
	 *
	 * @return array
	 */
	protected function combine_item_taxes( $item_taxes ) {
		$merged_taxes = array();
		foreach ( $item_taxes as $taxes ) {
			foreach ( $taxes as $tax_id => $tax_amount ) {
				if ( ! isset( $merged_taxes[$tax_id] ) ) {
					$merged_taxes[$tax_id] = 0;
				}
				$merged_taxes[$tax_id] += $tax_amount;
			}
		}

		return $merged_taxes;
	}

	/*
	|--------------------------------------------------------------------------
	| Calculation methods.
	|--------------------------------------------------------------------------
	*/

	/**
	 * Calculate item totals.
	 *
	 * @since 3.2.0
	 */
	protected function calculate_item_totals() {
		$this->get_items_from_cart();
		$this->calculate_item_subtotals();
		$this->calculate_discounts();

		foreach ( $this->items as $item_key => $item ) {
			$item->total     = $this->get_discounted_price_in_cents( $item_key );
			$item->total_tax = 0;

			if ( has_filter( 'woocommerce_get_discounted_price' ) ) {
				/**
				 * Allow plugins to filter this price like in the legacy cart class.
				 *
				 * This is legacy and should probably be deprecated in the future.
				 * $item->object is the cart item object.
				 * $this->cart is the cart object.
				 */
				$item->total = wc_add_number_precision(
					apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->cart )
				);
			}

			if ( $this->calculate_tax && $item->product->is_taxable() ) {
				$item->taxes     = WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax );
				$item->total_tax = array_sum( $item->taxes );

				if ( ! $this->round_at_subtotal() ) {
					$item->total_tax = wc_round_tax_total( $item->total_tax, wc_get_rounding_precision() );
				}

				if ( $item->price_includes_tax ) {
					$item->total = $item->total - $item->total_tax;
				} else {
					$item->total = $item->total;
				}
			}

			$this->cart->cart_contents[$item_key]['line_tax_data']['total'] = wc_remove_number_precision_deep( $item->taxes );
			$this->cart->cart_contents[$item_key]['line_total']             = wc_remove_number_precision( $item->total );
			$this->cart->cart_contents[$item_key]['line_tax']               = wc_remove_number_precision( $item->total_tax );
		}

		$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
		$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );

		$this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
		$this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
		$this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
	}

	/**
	 * Subtotals are costs before discounts.
	 *
	 * To prevent rounding issues we need to work with the inclusive price where possible.
	 * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
	 * be 8.325 leading to totals being 1p off.
	 *
	 * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
	 * afterwards.
	 *
	 * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
	 *
	 * @since 3.2.0
	 */
	protected function calculate_item_subtotals() {
		foreach ( $this->items as $item_key => $item ) {
			if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
				$item = $this->adjust_non_base_location_price( $item );
			}

			$subtotal_taxes = array();

			if ( $this->calculate_tax && $item->product->is_taxable() ) {
				$subtotal_taxes     = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
				$item->subtotal_tax = array_sum( $subtotal_taxes );

				if ( ! $this->round_at_subtotal() ) {
					$item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, wc_get_rounding_precision() );
				}

				if ( $item->price_includes_tax ) {
					$item->subtotal = $item->subtotal - $item->subtotal_tax;
				}
			}

			$this->cart->cart_contents[$item_key]['line_tax_data']     = array( 'subtotal' => wc_remove_number_precision_deep( $subtotal_taxes ) );
			$this->cart->cart_contents[$item_key]['line_subtotal']     = wc_remove_number_precision( $item->subtotal );
			$this->cart->cart_contents[$item_key]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
		}
		$this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) );
		$this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) );

		$this->cart->set_subtotal( $this->get_total( 'items_subtotal' ) );
		$this->cart->set_subtotal_tax( $this->get_total( 'items_subtotal_tax' ) );
	}

	/**
	 * Calculate COUPON based discounts which change item prices.
	 *
	 * @since 3.2.0
	 * @uses  WC_Discounts class.
	 */
	protected function calculate_discounts() {
		$this->get_coupons_from_cart();

		$discounts = new WC_Discounts( $this->cart );

		foreach ( $this->coupons as $coupon ) {
			$discounts->apply_coupon( $coupon );
		}

		$coupon_discount_amounts     = $discounts->get_discounts_by_coupon( true );
		$coupon_discount_tax_amounts = array();

		// See how much tax was 'discounted' per item and per coupon.
		if ( $this->calculate_tax ) {
			foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
				$coupon_discount_tax_amounts[$coupon_code] = 0;

				foreach ( $coupon_discounts as $item_key => $coupon_discount ) {
					$item = $this->items[$item_key];

					if ( $item->product->is_taxable() ) {
						$item_tax                                  = array_sum( WC_Tax::calc_tax( $coupon_discount, $item->tax_rates, $item->price_includes_tax ) );
						$coupon_discount_tax_amounts[$coupon_code] += $item_tax;
					}
				}

				if ( wc_prices_include_tax() ) {
					$coupon_discount_amounts[$coupon_code] -= $coupon_discount_tax_amounts[$coupon_code];
				}
			}
		}

		$this->coupon_discount_totals     = (array) $discounts->get_discounts_by_item( true );
		$this->coupon_discount_tax_totals = $coupon_discount_tax_amounts;

		$this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
		$this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );

		$this->cart->set_coupon_discount_totals( wc_remove_number_precision_deep( $coupon_discount_amounts ) );
		$this->cart->set_coupon_discount_tax_totals( wc_remove_number_precision_deep( $coupon_discount_tax_amounts ) );
	}

	/**
	 * Triggers the cart fees API, grabs the list of fees, and calculates taxes.
	 *
	 * Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed.
	 *
	 * @since 3.2.0
	 */
	protected function calculate_fee_totals() {
		$this->get_fees_from_cart();

		$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
		$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );

		$this->cart->fees_api()->set_fees( wp_list_pluck( $this->fees, 'object' ) );
		$this->cart->set_fee_total( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ) );
		$this->cart->set_fee_tax( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
		$this->cart->set_fee_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->fees, 'taxes' ) ) ) );
	}

	/**
	 * Calculate any shipping taxes.
	 *
	 * @since 3.2.0
	 */
	protected function calculate_shipping_totals() {
		$this->get_shipping_from_cart();
		$this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
		$this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );

		$this->cart->set_shipping_total( $this->get_total( 'shipping_total' ) );
		$this->cart->set_shipping_tax( $this->get_total( 'shipping_tax_total' ) );
		$this->cart->set_shipping_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->shipping, 'taxes' ) ) ) );
	}

	/**
	 * Main cart totals.
	 *
	 * @since 3.2.0
	 */
	protected function calculate_totals() {
		$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ) ) );

		// Add totals to cart object.
		$this->cart->set_discount_total( $this->get_total( 'discounts_total' ) );
		$this->cart->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
		$this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );

		// Allow plugins to hook and alter totals before final total is calculated.
		if ( has_action( 'woocommerce_calculate_totals' ) ) {
			do_action( 'woocommerce_calculate_totals', $this->cart );
		}

		// Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
		$this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
	}

	public static function get_price_child_tour( $cart_item ) {
		$child_price = 0;

		if ( isset( $cart_item['date_check_in'] ) && isset( $cart_item['date_check_out'] ) ) {
			$date_check_in  = TravelPhysUtility::convert_date_to_format_default( $cart_item['date_check_in'] );
			$date_check_out = TravelPhysUtility::convert_date_to_format_default( $cart_item['date_check_out'] );

			$phys_price_of_dates_option = get_post_meta( $cart_item['data']->get_id(), '_phys_price_of_dates_option', true );
			$phys_tour_price_dates_type = get_post_meta( $cart_item['data']->get_id(), '_phys_price_dates_type', true );

			if ( $phys_price_of_dates_option !== '' && $phys_tour_price_dates_type !== '' ) {
				try {
					$phys_price_of_dates_option_obj = json_decode( $phys_price_of_dates_option );

					if ( $phys_tour_price_dates_type === 'price_dates_range' && isset( $phys_price_of_dates_option_obj->price_dates_range ) ) {
						$date_check_in_obj             = new DateTime( $date_check_in );
						$date_check_out_obj            = new DateTime( $date_check_out );
						$date_next                     = $date_check_in_obj;
						$tour_price_dates_range_length = count( (array) $phys_price_of_dates_option_obj->price_dates_range );


						while ( $date_next < $date_check_out_obj ) {
							$count_tour_price_dates_range = 1;

							foreach ( $phys_price_of_dates_option_obj->price_dates_range as $k => $price_dates_range ) {
								$start_date = new DateTime( $price_dates_range->start_date );
								$end_date   = new DateTime( $price_dates_range->end_date );
								$flag_same  = false;

								if ( $date_next >= $start_date && $date_next <= $end_date ) {
									foreach ( $price_dates_range->prices as $k_price => $v_price ) {
										$price = $v_price->price;

										if ( $k_price == 'child_price_dates' ) {
											if ( $price == '' || (float) $price == 0 ) {
												$price = get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
											}

											$child_price += $price;

											break;
										}

									};

									$flag_same = true;
								}

								if ( $flag_same ) {
									break;
								}

								if ( $count_tour_price_dates_range == $tour_price_dates_range_length && ! $flag_same ) {
									$child_price += get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
								}

								$count_tour_price_dates_range ++;
							}

							$date_next = $date_next->add( new DateInterval( 'P1D' ) );
						}

					} elseif ( $phys_tour_price_dates_type === 'price_multiple_dates' && isset( $phys_price_of_dates_option_obj->price_multiple_dates ) ) {

					}
				}
				catch ( Exception $e ) {
					//							var_dump( $e );
				}
			}
		} elseif ( array_key_exists( 'date_booking', $cart_item ) ) {
			$child_price = get_post_meta( $cart_item['data']->get_id(), '_price_child', true );;
		}

		return apply_filters('phys_tour_child_price', $child_price);
	}

	public static function get_price_variation_tour( $cart_item ) {
		$price_variation_tour_total = 0;

		$tour_variations         = array_key_exists( 'tour_variations', $cart_item ) ? $cart_item['tour_variations'] : null;
		$tour_variations_options = array_key_exists( 'tour_variations_options', $cart_item ) ? $cart_item['tour_variations_options'] : null;

		/*** Get price days ***/
		$price_dates_tour = new stdClass();

		if ( array_key_exists( 'price_dates_tour', $cart_item ) ) {
			$price_dates_tour = $cart_item['price_dates_tour'];
		}

		if ( ! is_null( $tour_variations ) && ! is_null( $tour_variations_options ) && count( (array) $tour_variations_options ) > 0 ) {
			foreach ( $tour_variations as $key => $tour_variant ) {
				$tour_variant_item = $tour_variations_options->$key;
				if ( $tour_variant_item->set_price == 1 ) {
					if ( $tour_variant_item->type_variation === 'quantity' ) {
						foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
							$price_attr = $tour_variant_item->variation_attr->{$k_attr}->price;
							$qty_attr   = $tour_variant_attr->quantity;

							// price dates variation
							if ( isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
								$price_attr = $price_dates_tour->{$key}->{$k_attr};
							}

							$price_variation_tour_total += $price_attr * $qty_attr;
						}
					} elseif ( $tour_variant_item->type_variation === 'select' ) {
						foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
							$price_attr = $tour_variant_item->variation_attr->{$k_attr}->price;

							// price dates variation
							if ( isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
								$price_attr = $price_dates_tour->{$key}->{$k_attr};
							}

							$price_variation_tour_total += $price_attr;
						}
					} elseif ( $tour_variant_item->type_variation === 'checkbox' ) {
						foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
							$price_attr = $tour_variant_item->variation_attr->{$k_attr}->price;

							// price dates variation
							if ( isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
								$price_attr = $price_dates_tour->{$key}->{$k_attr};
							}

							$price_variation_tour_total += $price_attr;
						}
					} elseif ( $tour_variant_item->type_variation === 'radio' ) {
						foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
							$price_attr = $tour_variant_item->variation_attr->{$k_attr}->price;

							// price dates variation
							if ( isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
								$price_attr = $price_dates_tour->{$key}->{$k_attr};
							}

							$price_variation_tour_total += $price_attr;
						}
					}
				}
			}
		}

		return $price_variation_tour_total;
	}

	public static function get_price_dates_tour( $cart_item ) {
		/*** Get price days ***/
		$price_dates_tour = new stdClass();

		if ( array_key_exists( 'date_check_in', $cart_item ) && array_key_exists( 'date_check_out', $cart_item ) ) {
			$date_check_in      = TravelPhysUtility::convert_date_to_format_default( $cart_item['date_check_in'] );
			$date_check_out     = TravelPhysUtility::convert_date_to_format_default( $cart_item['date_check_out'] );
			$date_check_in_obj  = new DateTime( $date_check_in );
			$date_check_out_obj = new DateTime( $date_check_out );
			$date_next          = $date_check_in_obj;

			$phys_price_of_dates_option = get_post_meta( $cart_item['data']->get_id(), '_phys_price_of_dates_option', true );
			$phys_tour_price_dates_type = get_post_meta( $cart_item['data']->get_id(), '_phys_price_dates_type', true );

			if ( $phys_price_of_dates_option !== '' && $phys_tour_price_dates_type !== '' ) {
				try {
					$phys_price_of_dates_option_obj = json_decode( $phys_price_of_dates_option );

					if ( $phys_tour_price_dates_type === 'price_dates_range' && isset( $phys_price_of_dates_option_obj->price_dates_range ) ) {

						$tour_price_dates_range_length = count( (array) $phys_price_of_dates_option_obj->price_dates_range );

						while ( $date_next < $date_check_out_obj ) {
							$date_next_str                = $date_next->format( TravelPhysUtility::$_date_format_default );
							$count_tour_price_dates_range = 1;

							foreach ( $phys_price_of_dates_option_obj->price_dates_range as $k => $price_dates_range ) {
								$start_date = new DateTime( $price_dates_range->start_date );
								$end_date   = new DateTime( $price_dates_range->end_date );
								$flag_same  = false;

								if ( $date_next >= $start_date && $date_next <= $end_date ) {
									foreach ( $price_dates_range->prices as $k_price => $v_price ) {

										if ( $k_price != 'regular_price_dates' && $k_price != 'child_price_dates' ) {
											foreach ( $v_price as $k_attr => $v_attr ) {
												if ( ! isset( $price_dates_tour->{$k_price} ) ) {
													$price_dates_tour->{$k_price} = new stdClass();
												}

												if ( ! isset( $price_dates_tour->{$k_price}->{$k_attr} ) ) {
													$price_dates_tour->{$k_price}->{$k_attr} = (float) ( $v_attr->price );
												} else {
													$price_dates_tour->{$k_price}->{$k_attr} += (float) ( $v_attr->price );
												}
											}
										} elseif ( $k_price == 'child_price_dates' ) {
											$price = (float) $v_price->price;

											if ( $price == '' || $price == 0 ) {
												$price = get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
											}

											if ( ! isset( $price_dates_tour->child_price_dates ) ) {
												$price_dates_tour->child_price_dates = $price;
											} else {
												$price_dates_tour->child_price_dates += $price;
											}
										} elseif ( $k_price == 'regular_price_dates' ) {
											$price = (float) $v_price->price;

											if ( $price == '' || $price == 0 ) {
												$price = $price = $cart_item['data']->get_price();
											}

											if ( ! isset( $price_dates_tour->regular_price_dates ) ) {
												$price_dates_tour->regular_price_dates = $price;
											} else {
												$price_dates_tour->regular_price_dates += $price;
											}
										}
									};

									$flag_same = true;
								}

								if ( $flag_same ) {
									break;
								}

								// get price default
								if ( $count_tour_price_dates_range == $tour_price_dates_range_length && ! $flag_same ) {
									if ( ! isset( $price_dates_tour->regular_price_dates ) ) {
										$price_dates_tour->regular_price_dates = (float) $cart_item['data']->get_price();
									} else {
										$price_dates_tour->regular_price_dates += (float) $cart_item['data']->get_price();
									}

									if ( ! isset( $price_dates_tour->child_price ) ) {
										$price_dates_tour->child_price = (float) get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
									} else {
										$price_dates_tour->child_price += (float) get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
									}

									if ( array_key_exists( 'tour_variations', $cart_item ) && array_key_exists( 'tour_variations_options', $cart_item ) ) {
										$tour_variations         = $cart_item['tour_variations'];
										$tour_variations_options = $cart_item['tour_variations_options'];

										foreach ( $tour_variations as $key => $tour_variant ) {
											$tour_variant_item = $tour_variations_options->$key;

											if ( ! isset( $price_dates_tour->{$key} ) ) {
												$price_dates_tour->{$key} = new stdClass();
											}

											if ( $tour_variant_item->set_price == 1 ) {
												if ( $tour_variant_item->type_variation === 'quantity' ) {
													foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
														$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

														if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
															$price_dates_tour->{$key}->{$k_attr} = $price_attr;
														} else {
															$price_dates_tour->{$key}->{$k_attr} += $price_attr;
														}
													}
												} elseif ( $tour_variant_item->type_variation === 'select' ) {
													foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
														$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

														if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
															$price_dates_tour->{$key}->{$k_attr} = $price_attr;
														} else {
															$price_dates_tour->{$key}->{$k_attr} += $price_attr;
														}
													}
												} elseif ( $tour_variant_item->type_variation === 'checkbox' ) {
													foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
														$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

														if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
															$price_dates_tour->{$key}->{$k_attr} = $price_attr;
														} else {
															$price_dates_tour->{$key}->{$k_attr} += $price_attr;
														}
													}
												} elseif ( $tour_variant_item->type_variation === 'radio' ) {
													foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
														$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

														if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
															$price_dates_tour->{$key}->{$k_attr} = $price_attr;
														} else {
															$price_dates_tour->{$key}->{$k_attr} += $price_attr;
														}
													}
												}
											}
										}
									}
								}

								$count_tour_price_dates_range ++;
							}

							$date_next = $date_next->add( new DateInterval( 'P1D' ) );
						}

					} elseif ( $phys_tour_price_dates_type === 'price_multiple_dates' && isset( $phys_price_of_dates_option_obj->price_multiple_dates ) ) {

					}
				}
				catch ( Exception $e ) {
					var_dump( $e );
				}
			} else {
				while ( $date_next < $date_check_out_obj ) {
					// get price default
					if ( ! isset( $price_dates_tour->regular_price_dates ) ) {
						$price_dates_tour->regular_price_dates = (float) $cart_item['data']->get_price();
					} else {
						$price_dates_tour->regular_price_dates += (float) $cart_item['data']->get_price();
					}

					if ( ! isset( $price_dates_tour->child_price_dates ) ) {
						$price_dates_tour->child_price_dates = (float) get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
					} else {
						$price_dates_tour->child_price_dates += (float) get_post_meta( $cart_item['data']->get_id(), '_price_child', true );
					}

					if ( array_key_exists( 'tour_variations', $cart_item ) && array_key_exists( 'tour_variations_options', $cart_item ) ) {
						$tour_variations         = $cart_item['tour_variations'];
						$tour_variations_options = $cart_item['tour_variations_options'];

						foreach ( $tour_variations as $key => $tour_variant ) {
							$tour_variant_item = $tour_variations_options->$key;

							if ( ! isset( $price_dates_tour->{$key} ) ) {
								$price_dates_tour->{$key} = new stdClass();
							}

							if ( $tour_variant_item->set_price == 1 ) {
								if ( $tour_variant_item->type_variation === 'quantity' ) {
									foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
										$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

										if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
											$price_dates_tour->{$key}->{$k_attr} = $price_attr;
										} else {
											$price_dates_tour->{$key}->{$k_attr} += $price_attr;
										}
									}
								} elseif ( $tour_variant_item->type_variation === 'select' ) {
									foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
										$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

										if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
											$price_dates_tour->{$key}->{$k_attr} = $price_attr;
										} else {
											$price_dates_tour->{$key}->{$k_attr} += $price_attr;
										}
									}
								} elseif ( $tour_variant_item->type_variation === 'checkbox' ) {
									foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
										$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

										if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
											$price_dates_tour->{$key}->{$k_attr} = $price_attr;
										} else {
											$price_dates_tour->{$key}->{$k_attr} += $price_attr;
										}
									}
								} elseif ( $tour_variant_item->type_variation === 'radio' ) {
									foreach ( $tour_variant as $k_attr => $tour_variant_attr ) {
										$price_attr = (float) $tour_variant_item->variation_attr->{$k_attr}->price;

										if ( ! isset( $price_dates_tour->{$key}->{$k_attr} ) ) {
											$price_dates_tour->{$key}->{$k_attr} = $price_attr;
										} else {
											$price_dates_tour->{$key}->{$k_attr} += $price_attr;
										}
									}
								}
							}
						}
					}

					$date_next = $date_next->add( new DateInterval( 'P1D' ) );
				}
			}
		}

		return $price_dates_tour;
	}

	public static function get_total_number_children_booking() {
		$total_number_children = 0;

		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
			if ( isset( $cart_item['date_booking'] ) ) {
				if ( isset( $cart_item['number_children'] ) ) {
					$total_number_children += (int) $cart_item['number_children'];
				}
			}
		}

		return $total_number_children;
	}
}
